package com.bangcommunity.bbframe.common.utils.reflect;

import com.bangcommunity.bbframe.common.utils.DateUtils;
import com.bangcommunity.bbframe.common.utils.LRUCache;
import com.bangcommunity.bbframe.common.utils.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import static java.util.Locale.ENGLISH;

/**
 * 反射包装
 *
 * @author tanghc
 * @since 2015/11/6.
 */
public class ReflectionWrap {

    Logger logger = LoggerFactory.getLogger(ReflectionWrap.class);
    private final static ReflectionWrap instance = new ReflectionWrap();
    private final Map<Class<?>, Map<String, Field>> FIELD_CACHED = new LRUCache<Class<?>, Map<String, Field>>();
    private final Map<Class<?>, Map<String, Method>> CONVERT_METHOD_CACHED = new LRUCache<Class<?>, Map<String, Method>>();
    private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private final Lock writeLock = readWriteLock.writeLock();
    private final Lock writeMethodLock = readWriteLock.writeLock();
    static final String GET_PREFIX = "get";
    static final String IS_PREFIX = "is";
    /**
     * 转换bean属性类型设置
     */
    private boolean NULL_TO_EMPTY = true;
    private boolean DATE_TO_LONG = true;
    private boolean RECRU = true;
    private final String datePatten = "yyyy-MM-dd HH:mm:ss";
    private DateFormat dateFormat = new SimpleDateFormat(datePatten);

    public static final ReflectionWrap getInstance() {
        return instance;
    }

    private final PropertyConvertor defaultConvertor = new PropertyConvertor() {
        @Override
        public Object convert(String filedName, Method readMethod, Object methodReturn) {
            // null处理
            if (null == methodReturn) {
                if (NULL_TO_EMPTY) {
                    return "";
                } else {
                    return methodReturn;
                }
            }
            Class methodType = methodReturn.getClass();
            // Date类型处理
            if (methodType.equals(Date.class)) {
                Date date = (Date) methodReturn;
                if (DATE_TO_LONG) {
                    return date.getTime();
                } else if (null != dateFormat) {
                    try {
                        return dateFormat.format(date);
                    } catch (Exception e) {
                        logger.error("reflectionwrap format date fail: dataformat={},date={}",
                                new Object[] { dateFormat, date });
                    }
                    return "";
                } else {
                    return date.toString();
                }
            }
            // 枚举类型处理
            if (methodType.isEnum()) {
                Enum enumVal = (Enum) methodReturn;
                return enumVal.name();
            }
            // 自定义类型递归处理
            if (!isJavaClass(methodType) && RECRU) {
                return ReflectionWrap.this.convertBean(methodReturn);
            }
            return methodReturn;
        }
    };

    public Map<String, Field> getFields(Class<?> clazz) {
        writeLock.lock();
        try {
            if (!FIELD_CACHED.containsKey(clazz)) {
                fillFields(clazz);
            }
            return FIELD_CACHED.get(clazz);
        } finally {
            writeLock.unlock();
        }
    }

    public Map<String, Method> getConvertMethods(Class<?> clazz) {
        writeMethodLock.lock();
        try {
            if (!CONVERT_METHOD_CACHED.containsKey(clazz)) {
                fillConvertMethods(clazz);
            }
            return CONVERT_METHOD_CACHED.get(clazz);
        } finally {
            writeMethodLock.unlock();
        }
    }

    /**
     *
     * @param source
     * @param target
     * @param onlyNotNull
     *            是否只拷贝非空属性
     */
    public void copyBeanByField(final Object source, final Object target, final Boolean onlyNotNull,
            String[] ignoreProperties) {
        final Class<?> sourceClazz = source.getClass();
        final Class<?> targetClazz = target.getClass();

        Map<String, Field> sourceFields = getFields(sourceClazz);
        Map<String, Field> targetFields = getFields(targetClazz);

        List<String> fileds = (null == ignoreProperties ? null : Arrays.asList(ignoreProperties));
        for (Map.Entry<String, Field> entry : sourceFields.entrySet()) {
            String fieldName = entry.getKey();
            Field sourceField = entry.getValue();
            Field targetField = targetFields.get(fieldName);
            if (targetField == null) {
                continue;
            }
            if (fileds != null && fileds.contains(fieldName)) {
                continue;
            }
            sourceField.setAccessible(true);
            targetField.setAccessible(true);
            Object sourceValue = invokeField(sourceField, source);
            if (onlyNotNull) {
                if (null != sourceValue) {
                    setField(targetField, target, sourceValue);
                }
            } else {
                setField(targetField, target, sourceValue);
            }
        }
    }

    /**
     * 默认只拷贝非空属性
     *
     * @param source
     * @param target
     */
    public void copyBeanByField(final Object source, final Object target) {
        copyBeanByField(source, target, true, null);
    }

    public void setField(Field field, Object target, Object value) {
        try {
            field.setAccessible(true);
            field.set(target, value);
        } catch (Exception e) {
            // ignore
        }
    }

    public Object invokeField(Field field, Object target) {
        try {
            field.setAccessible(true);
            return field.get(target);
        } catch (Exception e) {
            // ignore
        }
        return null;
    }

    private void fillFields(Class<?> clazz) {
        final Map<String, Field> fields = new HashMap<String, Field>();
        FIELD_CACHED.put(clazz, fields);
        ReflectionUtils.doWithFields(clazz, new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                String fieldName = field.getName();
                if (!fields.containsKey(fieldName)) {
                    fields.put(fieldName, field);
                }
            }
        });
    }

    private void fillConvertMethods(Class<?> clazz) {
        final Map<String, Method> methods = new HashMap<String, Method>();
        final Map<String, String> ignorPropertyReadMethod = new HashMap<String, String>();
        Map<String, Field> fields = this.getFields(clazz);
        if (null != fields) {
            for (Map.Entry<String, Field> entry : fields.entrySet()) {
                Field field = entry.getValue();
                // PropertyIgnor的feild对应的get/is方法过滤掉
                ConvertIgnor propertyIgnor = field.getAnnotation(ConvertIgnor.class);
                if (null != propertyIgnor) {
                    String fieldName = field.getName();
                    String readMethodName = null;
                    Class type = field.getType();
                    if (type == boolean.class || type == null) {
                        readMethodName = IS_PREFIX + capitalize(fieldName);
                    } else {
                        readMethodName = GET_PREFIX + capitalize(fieldName);
                    }
                    ignorPropertyReadMethod.put(readMethodName, readMethodName);
                }
            }
        }
        ReflectionUtils.doWithMethods(clazz, new ReflectionUtils.MethodCallback() {
            @Override
            public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
                String name = method.getName();
                methods.put(readMethodToFieldName(method), method);
            }
        }, new ReflectionUtils.MethodFilter() {
            @Override
            public boolean matches(Method method) {
                // public的方法
                if (!Modifier.isPublic(method.getModifiers())) {
                    return false;
                }
                String name = method.getName();
                if (null == name || (!name.startsWith(IS_PREFIX) && !name.startsWith(GET_PREFIX))) {
                    return false;
                }
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 只处理getxxx/isxxx方法中，无传入参数的方法
                if (null != parameterTypes && parameterTypes.length > 0) {
                    return false;
                }
                // PropertyIgnor过滤掉
                ConvertIgnor propertyIgnor = method.getAnnotation(ConvertIgnor.class);
                if (null != propertyIgnor) {
                    return false;
                }
                // 属性上注解了 PropertyIgnor过滤掉
                if (null != ignorPropertyReadMethod.get(method.getName())) {
                    return false;
                }
                // class类型的数据过滤掉
                if (method.getReturnType().equals(Class.class)) {
                    return false;
                }
                return true;
            }
        });
        CONVERT_METHOD_CACHED.put(clazz, methods);
    }

    /**
     * 将一个 对象转化为一个 Map
     *
     * @return
     */
    public Map<String, Object> convertBean(Object bean) {
        return convertBean(bean, null);
    }

    /**
     * 将一个 对象转化为一个 Map
     *
     * @param bean
     *            需要转换的对象
     * @param convertor
     *            对某些列进行特殊处理
     * @return
     */
    public Map<String, Object> convertBean(Object bean, PropertyConvertor convertor) {
        if (null == bean) {
            return Collections.emptyMap();
        }
        Class type = bean.getClass();
        Map<String, Object> returnMap = new HashMap<String, Object>();
        Map<String, Method> convertMethods = getConvertMethods(type);
        if (null != convertMethods) {
            for (Map.Entry<String, Method> entry : convertMethods.entrySet()) {
                String fieldName = entry.getKey();
                Method method = entry.getValue();
                Class<?> returnType = method.getReturnType();
                Object returnValue = ReflectionUtils.invokeMethod(method, bean);
                if (null != convertor && null != returnValue) {
                    try {
                        returnValue = convertor.convert(fieldName, method, returnValue);
                    } catch (ConvertIgnorRuntimeException e) {
                        logger.debug("custom PropertyConvertor ignore: method={},fieldName={}",
                                new Object[] { method, fieldName });
                    } catch (Exception e) {
                        logger.error("custom PropertyConvertor exception: method={},fieldName={}",
                                new Object[] { method, fieldName, e });
                    }
                } else {
                    returnValue = defaultConvertor.convert(fieldName, method, returnValue);
                }
                if (null != returnValue) {
                    returnMap.put(fieldName, returnValue);
                }
            }
        }
        return returnMap;
    }

    public <E> E convertMap(Map<String, Object> map, Class<E> clazz, PropertyReflect<E> propertyReflect) {
        if (null == map || null == clazz) {
            return null;
        }
        E entity = null;
        try {
            if (null == propertyReflect) {
                propertyReflect = new PropertyReflect<E>() {
                    @Override
                    public void reflect(String filedName, Field field, Object bean, Object value) {
                        Class valueClass = value.getClass();
                        Class fieldClass = field.getType();
                        if (fieldClass.isAssignableFrom(valueClass)) {
                            setField(field, bean, value);
                        } else if (fieldClass.isAssignableFrom(Date.class)) {
                            if (value instanceof Number) {
                                Long aLong = new Long(value.toString());
                                if (null != aLong) {
                                    Date date = new Date(aLong);
                                    setField(field, bean, date);
                                }
                            } else if (value instanceof String) {
                                setField(field, bean, DateUtils.parse(value.toString(), datePatten));
                            }
                        } else if (fieldClass.isEnum()) {
                            if (value instanceof String) {
                                Enum anEnum = Enum.valueOf(fieldClass, value.toString());
                                setField(field, bean, anEnum);
                            }
                        } else if (value instanceof Map && !isJavaClass(fieldClass) && RECRU) {
                            try {
                                setField(field, bean,
                                        ReflectionWrap.this.convertMap((Map<String, Object>) value, fieldClass, null));
                            } catch (Exception e) {
                                logger.error("recru convertMap exception: value={}", value);
                            }
                        }
                    }
                };
            }
            entity = clazz.newInstance();
            Map<String, Field> fields = this.getFields(clazz);
            if (null != fields) {
                for (Map.Entry<String, Field> entry : fields.entrySet()) {
                    Field field = entry.getValue();
                    String fieldName = entry.getKey();
                    Object value = map.get(fieldName);
                    if (value == null || StringUtils.isBlank(value.toString())) {
                        continue;
                    }
                    propertyReflect.reflect(fieldName, field, entity, value);
                }
            }
        } catch (Exception e) {
            logger.error("convertMap excpetion: map={},clazz={}", map, clazz, e);
        }
        return entity;
    }

    /**
     * Returns a String which capitalizes the first letter of the string.
     */
    public static String capitalize(String name) {
        if (name == null || name.length() == 0) {
            return name;
        }
        return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
    }

    /**
     * Returns a String which capitalizes the first letter of the string.
     */
    public static String uncapitalize(String name, String prefix) {
        if (name == null || name.length() == 0) {
            return null;
        }
        if (null != prefix && prefix.length() > 0) {
            if (!name.startsWith(prefix)) {
                return null;
            }
            name = name.substring(prefix.length());
        }
        if (name == null || name.length() == 0) {
            return null;
        }
        return name.substring(0, 1).toLowerCase(ENGLISH) + name.substring(1);
    }

    /**
     * Returns a String which capitalizes the first letter of the string.
     */
    public static String readMethodToFieldName(Method method) {
        if (null == method) {
            return null;
        }
        String name = method.getName();
        if (name.startsWith(IS_PREFIX)) {
            return uncapitalize(name, IS_PREFIX);
        } else if (name.startsWith(GET_PREFIX)) {
            return uncapitalize(name, GET_PREFIX);
        }
        return null;
    }

    public boolean isNULL_TO_EMPTY() {
        return NULL_TO_EMPTY;
    }

    public void setNULL_TO_EMPTY(boolean NULL_TO_EMPTY) {
        this.NULL_TO_EMPTY = NULL_TO_EMPTY;
    }

    public boolean isDATE_TO_LONG() {
        return DATE_TO_LONG;
    }

    public void setDATE_TO_LONG(boolean DATE_TO_LONG) {
        this.DATE_TO_LONG = DATE_TO_LONG;
    }

    public DateFormat getDateFormat() {
        return dateFormat;
    }

    public void setDateFormat(DateFormat dateFormat) {
        this.dateFormat = dateFormat;
    }

    public static boolean isJavaClass(Class clz) {
        return clz != null && clz.getClassLoader() == null;
    }

    public boolean isRECRU() {
        return RECRU;
    }

    public void setRECRU(boolean RECRU) {
        this.RECRU = RECRU;
    }

    public static void main(String[] args) {
        ReflectionWrap instance = ReflectionWrap.getInstance();
    }
}
